package com.ouc.common.utils.file;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.ouc.common.entity.FileTreeList;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import com.ouc.common.config.RuoYiConfig;
import com.ouc.common.utils.DateUtils;
import com.ouc.common.utils.StringUtils;
import com.ouc.common.utils.uuid.IdUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.lang.NonNull;

/**
 * 文件处理工具类
 *
 * @author ouc
 */
public class FileUtils {
  public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";

  /**
   * 输出指定文件的byte数组
   *
   * @param filePath 文件路径
   * @param os       输出流
   * @return
   */
  public static void writeBytes(String filePath, OutputStream os) throws IOException {
    FileInputStream fis = null;
    try {
      File file = new File(filePath);
      if (!file.exists()) {
        throw new FileNotFoundException(filePath);
      }
      fis = new FileInputStream(file);
      byte[] b = new byte[1024];
      int length;
      while ((length = fis.read(b)) > 0) {
        os.write(b, 0, length);
      }
    } catch (IOException e) {
      throw e;
    } finally {
      IOUtils.close(os);
      IOUtils.close(fis);
    }
  }

  /**
   * 写数据到文件中
   *
   * @param data 数据
   * @return 目标文件
   * @throws IOException IO异常
   */
  public static String writeImportBytes(byte[] data) throws IOException {
    return writeBytes(data, RuoYiConfig.getImportPath());
  }

  /**
   * 写数据到文件中
   *
   * @param data      数据
   * @param uploadDir 目标文件
   * @return 目标文件
   * @throws IOException IO异常
   */
  public static String writeBytes(byte[] data, String uploadDir) throws IOException {
    FileOutputStream fos = null;
    String pathName = "";
    try {
      String extension = getFileExtendName(data);
      pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
      File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
      fos = new FileOutputStream(file);
      fos.write(data);
    } finally {
      IOUtils.close(fos);
    }
    return FileUploadUtils.getPathFileName(uploadDir, pathName);
  }

  /**
   * 删除文件
   *
   * @param filePath 文件
   * @return
   */
  public static boolean deleteFile(String filePath) {
    boolean flag = false;
    File file = new File(filePath);
    // 路径为文件且不为空则进行删除
    if (file.isFile() && file.exists()) {
      flag = file.delete();
    }
    return flag;
  }

  /**
   * 文件名称验证
   *
   * @param filename 文件名称
   * @return true 正常 false 非法
   */
  public static boolean isValidFilename(String filename) {
    return filename.matches(FILENAME_PATTERN);
  }

  /**
   * 检查文件是否可下载
   *
   * @param resource 需要下载的文件
   * @return true 正常 false 非法
   */
  public static boolean checkAllowDownload(String resource) {
    // 禁止目录上跳级别
    if (StringUtils.contains(resource, "..")) {
      return false;
    }

    // 检查允许下载的文件规则
    if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) {
      return true;
    }

    // 不在允许下载的文件规则
    return false;
  }

  /**
   * 下载文件名重新编码
   *
   * @param request  请求对象
   * @param fileName 文件名
   * @return 编码后的文件名
   */
  public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {
    final String agent = request.getHeader("USER-AGENT");
    String filename = fileName;
    if (agent.contains("MSIE")) {
      // IE浏览器
      filename = URLEncoder.encode(filename, "utf-8");
      filename = filename.replace("+", " ");
    } else if (agent.contains("Firefox")) {
      // 火狐浏览器
      filename = new String(fileName.getBytes(), "ISO8859-1");
    } else if (agent.contains("Chrome")) {
      // google浏览器
      filename = URLEncoder.encode(filename, "utf-8");
    } else {
      // 其它浏览器
      filename = URLEncoder.encode(filename, "utf-8");
    }
    return filename;
  }

  /**
   * 下载文件名重新编码
   *
   * @param response     响应对象
   * @param realFileName 真实文件名
   */
  public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {
    String percentEncodedFileName = percentEncode(realFileName);

    StringBuilder contentDispositionValue = new StringBuilder();
    contentDispositionValue.append("attachment; filename=")
        .append(percentEncodedFileName)
        .append(";")
        .append("filename*=")
        .append("utf-8''")
        .append(percentEncodedFileName);

    response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
    response.setHeader("Content-disposition", contentDispositionValue.toString());
    response.setHeader("download-filename", percentEncodedFileName);
  }

  /**
   * 百分号编码工具方法
   *
   * @param s 需要百分号编码的字符串
   * @return 百分号编码后的字符串
   */
  public static String percentEncode(String s) throws UnsupportedEncodingException {
    String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
    return encode.replaceAll("\\+", "%20");
  }

  /**
   * 获取图像后缀
   *
   * @param photoByte 图像数据
   * @return 后缀名
   */
  public static String getFileExtendName(byte[] photoByte) {
    String strFileExtendName = "jpg";
    if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
        && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) {
      strFileExtendName = "gif";
    } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) {
      strFileExtendName = "jpg";
    } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) {
      strFileExtendName = "bmp";
    } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) {
      strFileExtendName = "png";
    }
    return strFileExtendName;
  }

  /**
   * 获取文件名称 /profile/upload/2022/04/16/ouc.png -- ouc.png
   *
   * @param fileName 路径名称
   * @return 没有文件路径的名称
   */
  public static String getName(String fileName) {
    if (fileName == null) {
      return null;
    }
    int lastUnixPos = fileName.lastIndexOf('/');
    int lastWindowsPos = fileName.lastIndexOf('\\');
    int index = Math.max(lastUnixPos, lastWindowsPos);
    return fileName.substring(index + 1);
  }

  /**
   * 获取不带后缀文件名称 /profile/upload/2022/04/16/ouc.png -- ouc
   *
   * @param fileName 路径名称
   * @return 没有文件路径和后缀的名称
   */
  public static String getNameNotSuffix(String fileName) {
    if (fileName == null) {
      return null;
    }
    String baseName = FilenameUtils.getBaseName(fileName);
    return baseName;
  }


  /**
   * 获取文件扩展名
   *
   * @param fileName 文件名
   * @return 文件扩展名 如：txt,docx
   */
  public static String getFileExtensionName(@NonNull String fileName) {
    return fileName.substring(fileName.lastIndexOf('.') + 1);
  }

  /**
   * 获取文件名（不带后缀）
   *
   * @param fileName 文件名
   * @return 不带后缀的文件名
   */
  public static String getFileNameNoEx(@NonNull String fileName) {
    int dot = fileName.lastIndexOf('.');
    return dot == -1 ? fileName : fileName.substring(0, dot);
  }

  /**
   * 获取文件夹下的文件地址
   *
   * @param filePath
   * @return
   */
  public static List<String> getFileList(@NonNull String filePath) {
    File file = new File(filePath);
    if (!file.exists() || !file.isDirectory()) {
      return Collections.emptyList();
    }

    File[] files = file.listFiles();
    if (files.length == 0) {
      return null;
    }
    List<String> list = new ArrayList<>();
    for (File value : files) {
      try {
        list.add(value.getCanonicalPath());
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return list;
  }

  /**
   * 将文件转为字节流
   *
   * @param file 文件
   * @return
   */
  public static byte[] getFileByteArray(File file) {
    long fileSize = file.length();
    if (fileSize > Integer.MAX_VALUE) {
      System.out.println("文件过大");
      return null;
    }
    byte[] buffer = null;
    try (FileInputStream fis = new FileInputStream(file)) {
      buffer = new byte[(int) fileSize];
      int offset = 0;
      int numRead = 0;
      while (offset < buffer.length
          && (numRead = fis.read(buffer, offset, buffer.length - offset)) >= 0) {
        offset += numRead;
      }
      // 确保所有数据均被读取
      if (offset != buffer.length) {
        throw new IOException("无法读取所有文件 "
            + file.getName());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return buffer;
  }

  /**
   * 根据byte数组，生成文件
   * filePath  文件路径
   * fileName  文件名称（需要带后缀，如*.jpg、*.java、*.xml）
   */
  public static void getFile(byte[] bfile, String filePath) {
    try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(filePath)))) {
      File dir = new File(filePath);
      if (!dir.getParentFile().exists()) {//判断文件目录是否存在
        dir.mkdirs();
      }
      bos.write(bfile);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * 获取文件夹下面最新的修改时间的文件
   *
   * @param file 文件夹
   * @return
   */
  public static File getLastModifiedFile(@NonNull File file) {
    File[] files = file.listFiles();
    if (!file.isDirectory() || files.length == 0) {
      return null;
    }
    File latestFile = files[0];
    for (File file1 : files) {
      if (file1.lastModified() > latestFile.lastModified()) {
        latestFile = file1;
      }
    }
    return latestFile;
  }

  /**
   * 获取文件的创建时间，以毫秒返回
   *
   * @param file 文件
   * @return
   */
  public static Long getFileCreateTime(File file) {
    try {
      FileTime fileTime = Files.readAttributes(Paths.get(file.getCanonicalPath()), BasicFileAttributes.class).creationTime();
      return fileTime.toMillis();
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  /**
   * 输入流转为字节数组
   *
   * @param inputStream 传入的输入流
   * @return 字节数组
   * @throws IOException io异常
   */
  public static byte[] oucReadInputStream(InputStream inputStream) throws IOException {
    byte[] buffer = new byte[1024];
    int len;
    /**
     * 将数据写入字节数组的输出流。 当数据写入缓冲区时，缓冲区会自动增长
     */
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    //  数据读取一次读取1024，直到读到空
    while ((len = inputStream.read(buffer)) != -1) {
      bos.write(buffer, 0, len);
    }
    bos.close();
    return bos.toByteArray();
  }

  /**
   * 获取某个文件夹下面的所有文件
   * 通过递归调用可以找到所有文件，如果有文件夹会进入
   * 找到里面的文件
   *
   * @param filePath 文件路径
   * @return
   * @throws IOException
   */
  public static List<String> getFilePathFromDirectory(String filePath) throws IOException {
    List<String> resultList = new ArrayList<>();
    if (!new File(filePath).exists()) {
      return resultList;
    }
    if (new File(filePath).isFile()) {
      resultList.add(filePath);
    } else {
      for (int i = 0; i < new File(filePath).listFiles().length; i++) {
        resultList.addAll(getFilePathFromDirectory(new File(filePath).listFiles()[i].getCanonicalPath()));
      }
    }
    return resultList;
  }

  /**
   * 通过两个文件的文件名和文件大小
   * 简单判断文件是否相同
   *
   * @param file1 第一个文件
   * @param file2 第二个文件
   * @return
   */
  public static boolean isSameJudgeByNameAndSize(File file1, File file2) {
    if (!file1.exists() || !file2.exists()) {
      return false;
    } else {
      return (file1.getName().equals(file2.getName())) && (file1.length() == file2.length());
    }
  }

  /**
   * 获取文件大小
   * 默认是以M返回的
   *
   * @param filePath 文件路径
   * @param type     文件大小返回单位
   * @return 大小的数值
   */
  public static long getFileSize(String filePath, String type) {
    File file = new File(filePath);
    switch (type) {
      case "b":
        return file.length();
      case "k":
        return file.length() / 1024;
      case "g":
        return file.length() / 1024 / 1024 / 1024;
      default:
        return file.length() / 1024 / 1024;
    }

  }

  /**
   * 解压zip文件
   * 遍历zip文件的每个文件
   * zip文件里面应该只有文件没有文件夹
   *
   * @param zipFilePath   zip文件所在路径
   * @param destDirectory 解压缩后文件存放路径
   * @throws IOException
   */
  public static void unzipFile(String zipFilePath, String destDirectory) throws IOException {
    if (StringUtils.endsWith(FileUtils.getName(zipFilePath), "zip")) {
      return;
    }
    File destDir = new File(destDirectory);
    if (!destDir.exists()) {
      destDir.mkdirs();
    }
    ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
    ZipEntry entry = zipIn.getNextEntry();
    while (entry != null) {
      String filePath = destDirectory + File.separator + entry.getName();
      if (!entry.isDirectory()) {
        extractFile(zipIn, filePath);
      } else {
        File dir = new File(filePath);
        dir.mkdir();
      }
      zipIn.closeEntry();
      entry = zipIn.getNextEntry();
    }
    zipIn.close();
  }

  /**
   * zip文件解压（里面包含文件夹）
   *
   * @param srcFile     待解压文件夹/文件
   * @param destDirPath 解压路径
   */
  public static void unZipDirFiles(File srcFile, String destDirPath) throws Exception {
    try {

//      File srcFile = new File(inputFile);//获取当前压缩文件
      // 判断源文件是否存在
      if (!srcFile.exists()) {
        throw new Exception(srcFile.getPath() + "所指文件不存在");
      }
      ZipFile zipFile = new ZipFile(srcFile, Charset.forName("GBK"));//创建压缩文件对象
      //开始解压
      Enumeration<?> entries = zipFile.entries();
      while (entries.hasMoreElements()) {
        ZipEntry entry = (ZipEntry) entries.nextElement();
        // 如果是文件夹，就创建个文件夹
        if (entry.isDirectory()) {
          String dirPath = destDirPath + "/" + entry.getName();
          srcFile.mkdirs();
        } else {
          // 如果是文件，就先创建一个文件，然后用io流把内容copy过去
          File targetFile = new File(destDirPath + "/" + entry.getName());
          // 保证这个文件的父文件夹必须要存在
          if (!targetFile.getParentFile().exists()) {
            targetFile.getParentFile().mkdirs();
          }
          targetFile.createNewFile();
          // 将压缩文件内容写入到这个文件中
          InputStream is = zipFile.getInputStream(entry);
          FileOutputStream fos = new FileOutputStream(targetFile);
          int len;
          byte[] buf = new byte[1024];
          while ((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
          }
          // 关流顺序，先打开的后关闭
          fos.close();
          is.close();
        }
      }
    } catch (Exception e) {
      return;
    }
  }

  /**
   * 解压zip文件
   * 会放到解压文件同目录
   * 遍历zip文件的每个文件
   * zip文件里面应该只有文件没有文件夹
   *
   * @param zipFilePath zip文件所在路径
   * @throws IOException
   */
  public static void unzipFile(String zipFilePath) throws IOException {
    unzipFile(zipFilePath, new File(zipFilePath).getParent());
  }

  /**
   * 解压缩gz文件
   *
   * @param compressedFile 压缩文件
   * @param destFile       解压缩文件目录
   * @throws IOException
   */
  public static void ungzipFile(String compressedFile, String destFile) throws IOException {
    if (StringUtils.endsWith(FileUtils.getName(compressedFile), "zip")) {
      return;
    }
    FileInputStream fis = new FileInputStream(compressedFile);
    GZIPInputStream gis = new GZIPInputStream(fis);
    File destDir = new File(destFile);
    if (!destDir.exists()) {
      destDir.mkdirs();
    }
    String fileName = FileUtils.getName(compressedFile);
    FileOutputStream fos = new FileOutputStream(destFile + File.separator + FileUtils.getFileNameNoEx(fileName));
    byte[] buffer = new byte[1024];
    int len;
    while ((len = gis.read(buffer)) != -1) {
      fos.write(buffer, 0, len);
    }
    fos.close();
    gis.close();
    fis.close();
  }

  /**
   * 解压缩gz文件
   * 会放到压缩文件的同目录下
   *
   * @param compressedFile 压缩文件
   * @throws IOException
   */
  public static void ungzipFile(String compressedFile) throws IOException {
    ungzipFile(compressedFile, new File(compressedFile).getParent());
  }

  private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
    byte[] bytesIn = new byte[4096];
    int read = 0;
    while ((read = zipIn.read(bytesIn)) != -1) {
      bos.write(bytesIn, 0, read);
    }
    bos.close();
  }

  /**
   * 获取目录下的所有文件
   *
   * @param dir       目录
   * @param fileNames 文件名集合
   */
  public static void findFileList(File dir, List<String> fileNames) {
    if (!dir.exists() || !dir.isDirectory()) {// 判断是否存在目录
      return;
    }
    String[] files = dir.list();// 读取目录下的所有目录文件信息
    for (int i = 0; i < files.length; i++) {// 循环，添加文件名或回调自身
      File file = new File(dir, files[i]);
      if (file.isFile()) {// 如果文件
        fileNames.add(dir + File.separator + file.getName());// 添加文件全路径名
      } else {// 如果是目录
        findFileList(file, fileNames);// 回调自身继续查询
      }
    }
  }

  /**
   * 获取文件夹的占用空间，递归调用的方式逐个获取
   *
   * @param file
   * @return
   */
  public static Long getFilesLength(File file) {
    if (file.isFile()) {
      // 文件大小直接返回
      return file.length();
    } else {
      // 目录大小需要递归计算
      long size = 0;
      File[] fileList = file.listFiles();
      if (fileList != null) {
        for (File f : fileList) {
          size += getFilesLength(f);
        }
      }
      return size;
    }
  }

  /**
   * 获取某个文件夹下面的所有文件文件树
   */
  public static FileTreeList getFileTreeList(String filePath) {
    File file = new File(filePath);
    FileTreeList fileTreeListResult = new FileTreeList();
    if (file.isFile()) {
      fileTreeListResult.setFileName(file.getName());
      fileTreeListResult.setFilePath(filePath);
      fileTreeListResult.setFileSize(file.length() / 1024);
    } else if (file.isDirectory()) {
      String[] list = file.list();
      fileTreeListResult.setFileName(file.getName());
      fileTreeListResult.setFilePath(filePath);
      List<FileTreeList> fileTreeListChildren = new ArrayList<>();
      Long fileSize = 0L;
      for (int i = 0; i < list.length; i++) {
        fileTreeListChildren.add(getFileTreeList(filePath + File.separatorChar + list[i]));
        fileSize += fileTreeListChildren.get(i).getFileSize();
      }
      fileTreeListResult.setFileTreeChildren(fileTreeListChildren);
      fileTreeListResult.setFileSize(fileSize);
    }
    return fileTreeListResult;
  }

  /**
   * 获取某个文件夹下面的文件个数
   */
  public static Integer getFileNum(String filePath) {
    Integer fileNum = 0;
    File file = new File(filePath);
    if (file.isFile()) {
      fileNum = 1;
    } else if (file.isDirectory()) {
      String[] list = file.list();
      for (int i = 0; i < list.length; i++) {
        fileNum += getFileNum(filePath + File.separatorChar + list[i]);
      }
    }
    return fileNum;
  }

}
